﻿// <copyright file="ManagementTelegram.cs" company="Hallvard Korsgaard">
//  Microsoft Public License (Ms-PL). See license details here: http://enoceannet.codeplex.com/license
// </copyright>
// <author>Hallvard Korsgaard</author>

namespace StcEthernet.Management
{
  using System;
  using System.Globalization;
  using System.Text;
  using EnOcean;

  /// <summary>
  /// This is the base class for all management telegram types. It contatins common properties
  /// and methods for parsing and serializing telegrams to the format required by
  /// the Thermokon STC-Ethernet module.
  /// </summary>
  [Serializable]
  public abstract class ManagementTelegram
  {
    /// <summary>
    /// Parses the ASCII representation of a message in byte format (as returned from the ToString() method).
    /// </summary>
    /// <param name="message">The ASCII representation of a message in byte format.</param>
    /// <returns>The object representation of the message.</returns>
    public static ManagementTelegram Parse(string message)
    {
      byte[] m;

      if (string.IsNullOrEmpty(message))
      {
        throw new ArgumentNullException("message");
      }

      if (message.Length != 28)
      {
        throw new ArgumentOutOfRangeException("message", "Message should be 28 characters.");
      }

      m = new byte[14];

      for (int i = 0; i < 14; i++)
      {
        m[i] = byte.Parse(message.Substring(2 * i, 2), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo);
      }

      return Parse(m);
    }

    /// <summary>
    /// Parses a message in byte format.
    /// </summary>
    /// <param name="message">A message in byte format (14 bytes).</param>
    /// <returns>The object representation of the message.</returns>
    public static ManagementTelegram Parse(byte[] message)
    {
      if (message == null)
      {
        throw new ArgumentNullException("message");
      }

      if (message.Length != 14)
      {
        throw new ArgumentOutOfRangeException("message", "Message should be 14 bytes.");
      }

      if (!(message[0] == 0xA5 && (message[1] != 0x5A || message[1] != 0x5B)))
      {
        throw new ArgumentException("Invalid sync bytes.");
      }

      if (message[2] == 0x9F)
      {
        return new InformMode()
        {
          Mode = (OperatingMode)message[3]
        };
      }

      if (message[2] == 0x98)
      {
        return new InformTimeError()
        {
          Org = (Org)message[3],
          IsMonitorTimerOn = message[4] == 0xF0,
          MonitorInterval = message[5],
          SensorFailureFromTimeToTime = message[6],
          HasFailed = message[7] == 0x01,
          Id = ParseId(message[8], message[9], message[10], message[11]),
          Channel = message[13]
        };
      }

      throw new ArgumentOutOfRangeException("Unknown telegram type");
    }

    /// <summary>
    /// Creates the byte representation of the telegram.
    /// </summary>
    /// <returns>A byte array of length 10 for all management telegrams, except InformTimeError that har a length of 14.</returns>
    public byte[] GetBytes()
    {
      byte[] message;

      if (this is InformTimeError)
      {
        message = new byte[14];
      }
      else
      {
        message = new byte[10];
      }

      this.GetBytes(message);

      return message;
    }

    /// <summary>
    /// Creates the byte representation of the telegram.
    /// </summary>
    /// <param name="message">A byte array of length 10 for all management telegrams.</param>
    public void GetBytes(byte[] message)
    {
      this.CreateMessage(message);
    }

    /// <summary>
    /// Returns the ASCII representation of the message in byte format.
    /// </summary>
    /// <returns>The ASCII representation of the message in byte format.</returns>
    public override string ToString()
    {
      byte[] message;

      if (this is InformTimeError)
      {
        message = new byte[28];
      }
      else
      {
        message = new byte[20];
      }

      this.GetBytes(message);

      for (int i = (message.Length / 2) - 1; i >= 0; i--)
      {
        message[(2 * i) + 1] = ConvertToAsciiChar((byte)(message[i] & 0xf));
        message[(2 * i)] = ConvertToAsciiChar((byte)(message[i] >> 4));
      }

      return ASCIIEncoding.ASCII.GetString(message);
    }

    /// <summary>
    /// Creates the byte format of the message.
    /// </summary>
    /// <param name="message">A byte array of length 10 to hold the message contents.</param>
    protected internal virtual void CreateMessage(byte[] message)
    {
      if (message == null)
      {
        throw new ArgumentNullException("message");
      }

      if (message.Length < 10)
      {
        throw new ArgumentOutOfRangeException("message", "array length must be at least 10 to hold the message content.");
      }

      message[0] = 0xA5; // Sync
      message[1] = 0x5A; // Sync
    }

    /// <summary>
    /// Converts a 4 bit number to the ASCII representation.
    /// </summary>
    /// <param name="value">A value in the  range 0x0 to 0xF inclusive.</param>
    /// <returns>The ASCII value representing the number.</returns>
    private static byte ConvertToAsciiChar(byte value)
    {
      if (value >= 0x0 && value <= 0x9)
      {
        return (byte)(value + 0x30);
      }
      else if (value >= 0xA && value <= 0XF)
      {
        return (byte)(value + 0x37);
      }
      else
      {
        throw new ArgumentOutOfRangeException("value", "must be in the range 0x0 to 0xF inclusive");
      }
    }

    /// <summary>
    /// Creates the message ID from the four individual bytes.
    /// </summary>
    /// <param name="b3">Byte 3 of the ID. This is the MSB byte.</param>
    /// <param name="b2">Byte 2 of the ID.</param>
    /// <param name="b1">Byte 1 of the ID.</param>
    /// <param name="b0">Byte 0 of the ID. This is the LSB byte.</param>
    /// <returns>The message ID.</returns>
    private static long ParseId(byte b3, byte b2, byte b1, byte b0)
    {
      long id;

      id = b3;
      id <<= 8;

      id |= b2;
      id <<= 8;

      id |= b1;
      id <<= 8;

      id |= b0;

      return id;
    }

    /// <summary>
    /// Creates the modem ID from the two individual bytes.
    /// </summary>
    /// <param name="b1">Byte 1 of the ID. This is the MSB byte.</param>
    /// <param name="b0">Byte 0 of the ID. This is the LSB byte</param>
    /// <returns>The modem ID.</returns>
    private static int ParseId(byte b1, byte b0)
    {
      int id;

      id = b1;
      id <<= 8;

      id |= b0;

      return id;
    }
  }
}
